package com.i360day.invoker;

import com.i360day.invoker.common.StringUtils;
import com.i360day.invoker.exception.HttpInvokerException;
import com.i360day.invoker.exception.RemoteAccessException;
import com.i360day.invoker.exception.RemoteConnectFailureException;
import com.i360day.invoker.exception.RemoteInvocationFailureException;
import com.i360day.invoker.executor.HttpInvokerRequestExecutor;
import com.i360day.invoker.proxy.TargetProxy;
import com.i360day.invoker.support.RemoteInvocation;
import com.i360day.invoker.support.RemoteInvocationFactory;
import com.i360day.invoker.support.RemoteInvocationResult;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.io.IOException;
import java.io.InvalidClassException;
import java.net.ConnectException;
import java.util.Optional;

/**
 * <p> @description: spring 方法代理，支持spring AOP逻辑    <p>
 * <p>
 * <p> @author: 胡.青牛   <p>
 * <p>
 * <p> @date: 2019/5/10 0010  15:42
 **/
class HttpInvokerClientMethodInterceptor implements MethodInterceptor {

    private HttpInvokerRequestExecutor httpInvokerRequestExecutor;

    private TargetProxy targetProxy;

    private RemoteInvocationFactory remoteInvocationFactory;

    public HttpInvokerClientMethodInterceptor(TargetProxy targetProxy, HttpInvokerRequestExecutor httpInvokerRequestExecutor, RemoteInvocationFactory remoteInvocationFactory) {
        this.targetProxy = targetProxy;
        this.remoteInvocationFactory = remoteInvocationFactory;
        this.httpInvokerRequestExecutor = httpInvokerRequestExecutor;
    }

    /**
     * <p> @Description:  <p>
     *
     * <p> @author: 青牛.胡 <p>
     *
     * <p> @Date:   2019/6/24 0024 10:32 <p>
     *
     * <p> @param ex      具体错误 </p>
     * <p> @param config  config </p>
     *
     * <p> @return:  错误  <p>
     **/
    protected RemoteAccessException convertHttpInvokerAccessException(Throwable ex) {
        if (ex instanceof ConnectException) {
            return new RemoteConnectFailureException(
                    "Could not connect to HTTP invoker remote service at [" + targetProxy.getUrl() + "]", ex);
        }

        if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ||
                ex instanceof InvalidClassException) {
            return new RemoteAccessException(
                    "Could not deserialize result from HTTP invoker remote service [" + targetProxy.getUrl() + "]", ex);
        }

        if (ex instanceof IOException) {
            return new RemoteAccessException(
                    ex.getMessage() + "，Could not access HTTP invoker remote service at [" + targetProxy.getUrl() + "]", ex);
        }

        if (ex instanceof Exception) {
            return new RemoteAccessException(
                    "Could not access HTTP invoker remote service at [" + targetProxy.getUrl() + "]", ex);
        }
        // For any other Throwable, e.g. OutOfMemoryError: let it get propagated as-is.
        return null;
    }

    /**
     * <p> @Description: 配置相关参数，发起预请求   <p>
     *
     * <p> @author: 胡.青牛   <p>
     *
     * <p> @Date:   2019/5/11 0011 09:55   <p>
     *
     * <p> @param null     <p>
     *
     * <p> @return:      <p>
     **/
    protected Object execute(RequestTemplate requestTemplate) throws Throwable {
        RemoteInvocationResult result;
        //网络异常
        try {
            result = httpInvokerRequestExecutor.executeRequest(requestTemplate);
        } catch (Throwable ex) {
            RemoteAccessException rae = convertHttpInvokerAccessException(ex);
            throw (rae != null ? rae : ex);
        }
        //数据异常
        try {
            return result.recreate();
        } catch (Throwable ex) {
            if (result.hasInvocationTargetException()) {
                throw ex;
            } else {
                throw new RemoteInvocationFailureException("Invocation of method [" + targetProxy.getName() + "] failed in HTTP invoker remote service at [" + targetProxy.getUrl() + "]", ex);
            }
        }
    }

    /**
     * 执行代理接口方法
     *
     * @param invocation the method invocation joinpoint
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        return Optional.ofNullable(StringUtils.isToString(this, invocation)).orElseGet(() -> {
            try {
                RemoteInvocation remoteInvocation = remoteInvocationFactory.createRemoteInvocation(invocation);
                MethodMetadata methodMetadata = MethodMetadata.of(invocation.getMethod(), invocation.getArguments());

                //构建templete
                RequestTemplate requestTemplate = RequestTemplate.of(targetProxy.getUrl(), targetProxy.getType(), methodMetadata, remoteInvocation);
                return execute(requestTemplate);
            } catch (Throwable throwable) {
                throw new HttpInvokerException(throwable);
            }
        });
    }


    @Override
    public String toString() {
        return targetProxy.toString();
    }
}
